/*
	Copyright (c) 2010 <copyright holders>
	This source code is licensed under The MIT License,
	see the file LICENSE for details.
	*/

#include "Landscape.hpp"
#include "PathFinder.hpp"
#include "BundleInfo.hpp"

#include <boost/property_tree/info_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <OGRE/Ogre.h>

#include "FixedTerrainHeader.hpp"
#include "HelpDefs.hpp"
#include "PresentationDefs.hpp"

namespace spelprog2 {

class LandscapeSpatialsImpl : public LandscapeSpatials {
    Ogre::TerrainSceneManager & sceneMan_;
    pathfind::NodeMesh *nodeMesh_;
    
public:
    LandscapeSpatialsImpl(Landscape & landscape);
    virtual sim::Int2d getExtents() const;
    virtual sim::Int getAltitude(const sim::Int2d & position) const;
    virtual Ogre::Real getAltitude(const Ogre::Vector2 & position) const;
    virtual sim::Int getInclination(const sim::Int2d & position, const sim::Int2d & direction) const;
    virtual Ogre::Radian getInclination(const Ogre::Vector2 & position, const Ogre::Vector2 & direction) const;
    virtual bool isBlockedForGroundUnits(sim::Int x, sim::Int y) const;
    //virtual bool isBlockedForBuildings(sim::Int x, sim::Int y) const;
    virtual void findGroundUnitPath(const sim::Int2d & origin, const sim::Int2d & destination, PointList & path) const;
    virtual void initPathFinder();
};

void LandscapeSpatialsImpl::initPathFinder()
{
    if(!nodeMesh_){
        nodeMesh_ = new pathfind::NodeGrid((LandscapeSpatials*) this, 100, 100, int(256 * 10));
    }
}

LandscapeSpatialsImpl::LandscapeSpatialsImpl(Landscape & landscape) : LandscapeSpatials(),
    sceneMan_(landscape.sceneMan_),
    nodeMesh_(0)
{
}

sim::Int2d LandscapeSpatialsImpl::getExtents() const
{
    return sim::Int2d(sim::Int(sceneMan_.getPageSize()) * sim::SCENE_TO_WORLD_SCALER,
        sim::Int(sceneMan_.getPageSize()) * sim::SCENE_TO_WORLD_SCALER);
}

Ogre::Real LandscapeSpatialsImpl::getAltitude(const Ogre::Vector2 & position) const
{
    return sceneMan_.getHeightAt(position.x, position.y);
}

sim::Int LandscapeSpatialsImpl::getAltitude(const sim::Int2d & position) const
{
    int x = position.x, y = position.y;
    Ogre::Real sceneX = Ogre::Real(position.x) / Ogre::Real(sim::SCENE_TO_WORLD_SCALER);
    Ogre::Real sceneY = Ogre::Real(position.y) / Ogre::Real(sim::SCENE_TO_WORLD_SCALER);
    return sim::Int(sceneMan_.getHeightAt(sceneX, sceneY) * Ogre::Real(sim::SCENE_TO_WORLD_SCALER));
}

sim::Int LandscapeSpatialsImpl::getInclination(const sim::Int2d & position, const sim::Int2d & direction) const
{
    //simply convert position and call FP version
    const Ogre::Vector2 dir(direction.x, direction.y); //since it's normalized no scaling is required
    const Ogre::Vector2 pos(Ogre::Real(position.x) / Ogre::Real(sim::SCENE_TO_WORLD_SCALER),
        Ogre::Real(position.y) / Ogre::Real(sim::SCENE_TO_WORLD_SCALER));
    const sim::Int incline(getInclination(pos, dir).valueRadians() / OGRE_PI * Ogre::Real(sim::ANGULAR_UNITS_PER_HALFCIRCLE));

    assert(incline >= -sim::ANGULAR_UNITS_PER_HALFCIRCLE / sim::Int(2));
    assert(incline <= +sim::ANGULAR_UNITS_PER_HALFCIRCLE / sim::Int(2));
    return incline;
}

Ogre::Radian LandscapeSpatialsImpl::getInclination(const Ogre::Vector2 & position, const Ogre::Vector2 & direction) const
{
    const Ogre::Real INCLINATION_MEASUREMENT_DELTA = 2.0f;
    const Ogre::Vector2 delta = direction.normalisedCopy() * INCLINATION_MEASUREMENT_DELTA;
    const Ogre::Real a = getAltitude(position);
    const Ogre::Real b = getAltitude(position + delta);
    const Ogre::Real h = b - a;
    const Ogre::Radian incline(std::atan(h / INCLINATION_MEASUREMENT_DELTA));

    assert(incline >= -Ogre::Radian(OGRE_PI / 2));
    assert(incline <= +Ogre::Radian(OGRE_PI / 2));
    return incline;
}

bool LandscapeSpatialsImpl::isBlockedForGroundUnits(sim::Int x, sim::Int y) const
{
    return false;
}

void LandscapeSpatialsImpl::findGroundUnitPath(const sim::Int2d & origin, const sim::Int2d & destination,
    PointList & path) const
{
    using namespace pathfind;
    
    path.clear();
    SearchNodeList searchList(nodeMesh_->getNumNodes());
    
    SearchNode *current, *target;
    current = searchList.getSearchNode(nodeMesh_->getNodeAtPosition(origin), 0, 0);
    target = searchList.getSearchNode(nodeMesh_->getNodeAtPosition(destination), 0, 0);
    
    int g = 0;
    while(current && current != target)
    {
        searchList.closeNode(current);
        const std::vector<Node*> &branches = current->getNodeBranches();
        for(size_t i = 0; i < branches.size(); i++)
        {
            searchList.pushOpenNode(branches[i], current, 0);
        }
        current = searchList.popOpenNode();
        g++;
    }
    if(current) current->tracePath(path);
}



void Landscape::loadLandscape(const boost::property_tree::ptree & landscapeNode)
{
    using namespace boost::property_tree;

    //load terrain
    const std::string & terrainFile = getBundlePath(RESOURCE_DEVIATION) + landscapeNode.get<std::string>("terrain");
    sceneMan_.setWorldGeometry(terrainFile);

    //get sky material
    const std::string & skyMat = landscapeNode.get<std::string>("sky");
    sceneMan_.setSkyBox(true, skyMat);
    
    //scenery objects
    const ptree & sceneryNode = landscapeNode.get_child("scenery");
    for (ptree::const_iterator it = sceneryNode.begin(), end = sceneryNode.end(); it != end; ++it) {
        if (it->first == "object") {
            loadObject(it->second);
        }
    }
}

void Landscape::loadObject(const boost::property_tree::ptree & objectNode)
{
    using namespace boost::property_tree;

    const std::string & meshName = objectNode.get<std::string>("mesh");

    Ogre::Vector3 scale;
    boost::optional<const ptree &> scaleNode = objectNode.get_child_optional("scale");
    if (scaleNode) {
        scale = loadVec3(scaleNode.get());
    }
    else {
        const float uniformScale = objectNode.get("scale_uniform", 1.0f);
        scale.x = scale.y = scale.z = uniformScale;
    }

    Ogre::Vector3 position;
    boost::optional<const ptree &> positionNode = objectNode.get_child_optional("position_2d");
    if (positionNode) {
        Ogre::Vector2 p = loadVec2(positionNode.get());
        position.x = p.x;
        position.z = p.y;

        position.y = objectNode.get("vertical_offset", Ogre::Real(1.0f)) + spatials_->getAltitude(p);
    }
    else {
        position = loadVec3(objectNode.get_child("position"));
    }

    Ogre::Radian yaw    (objectNode.get("yaw", Ogre::Real(0)));
    Ogre::Radian pitch  (objectNode.get("pitch", Ogre::Real(0)));
    Ogre::Radian roll   (objectNode.get("roll", Ogre::Real(0)));

    //create and add object to scene manager
    Ogre::Entity * const ent = sceneMan_.createEntity(meshName);
    ent->setQueryFlags(presentation::ENTITY_QUERY_FLAG_SCENERY);
    Ogre::SceneNode * const sceneNode = sceneMan_.getRootSceneNode()->createChildSceneNode(position);
    sceneNode->setScale(scale);
    sceneNode->yaw      (yaw, Ogre::Node::TS_WORLD);
    sceneNode->pitch    (pitch, Ogre::Node::TS_WORLD);
    sceneNode->roll     (roll, Ogre::Node::TS_WORLD);
    sceneNode->attachObject(ent);
}

Ogre::Vector2 Landscape::loadVec2(const boost::property_tree::ptree & vecNode)
{
    return Ogre::Vector2(vecNode.get<Ogre::Real>("x"), vecNode.get<Ogre::Real>("y"));
}

Ogre::Vector3 Landscape::loadVec3(const boost::property_tree::ptree & vecNode)
{
    return Ogre::Vector3(vecNode.get<Ogre::Real>("x"),
        vecNode.get<Ogre::Real>("y"), vecNode.get<Ogre::Real>("z"));
}

Landscape::Landscape(const Ogre::String & filename, Ogre::SceneManager & scene) :
    sceneMan_(dynamic_cast<Ogre::TerrainSceneManager &>(scene)),
    spatials_()
{
    //create spatials
    spatials_.reset(new LandscapeSpatialsImpl(*this));

    boost::property_tree::ptree landscapeFile;
    boost::property_tree::read_info(filename, landscapeFile);
    loadLandscape(landscapeFile.get_child("landscape")); //uses spatials

    //test lights
    sceneMan_.setAmbientLight(Ogre::ColourValue(0.3f, 0.3f, 0.3f));

    //Setup shadows
    sceneMan_.setAmbientLight(Ogre::ColourValue(0, 0, 0));
    sceneMan_.setShadowColour(Ogre::ColourValue(0, 0, 0));
    sceneMan_.setShadowTechnique(Ogre::SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED);
    sceneMan_.setShadowTextureSize(2048);
    
    // This value needs to be tweaked with the shadowFarDistance of the light!
    sceneMan_.setShadowDirLightTextureOffset(1.75);

    Ogre::Vector3 lightdir(-1, -1, -0.5);
	lightdir.normalise();
	Ogre::Light & light = *sceneMan_.createLight();
	light.setType(Ogre::Light::LT_DIRECTIONAL);
	light.setDirection(lightdir);
	light.setDiffuseColour(Ogre::ColourValue::White);
	light.setSpecularColour(Ogre::ColourValue(0.7f, 0.5f, 0.5f));
	
	// This is very important to do! It helps configuring the texture shadows.
	light.setShadowFarDistance(300);
}

LandscapeSpatials & Landscape::getSpatials() const
{
    return *spatials_.get();
}

Landscape::~Landscape()
{
}

}; //namespace spelprog2
